feat(get-state-by-cep): add getStateByCep, the state that owns a CEP range - #562
hyanmandian wants to merge 3 commits into
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughChangesCEP state lookup
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant Caller
participant getStateByCep
participant CEP_RANGES
participant DATA
Caller->>getStateByCep: Submit string or number CEP
getStateByCep->>CEP_RANGES: Match the parsed CEP
CEP_RANGES-->>getStateByCep: Return StateCode or no match
getStateByCep->>DATA: Resolve state metadata
DATA-->>getStateByCep: Return state data
getStateByCep-->>Caller: Return cloned State or null
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The new offline CEP lookup maps supported ranges to state data and returns null for invalid or uncovered values; its public API and documentation are aligned, so it is ready to merge. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Tree-shaking report✅ No size regression. 1 new out of 163 exports.
What changed (1)
All exports (163)
How this is measuredEvery export is imported alone into an esbuild consumer bundle (minified, tree-shaken) built from the head and from the base of this pull request; the sizes are the resulting bundles, gzip is their gzipped size. 🔴 marks a regression: a pre-existing export that grew more than 20% and more than 256 B, or the bundle importing every pre-existing export growing more than 5%. 🟡 is growth under the threshold, 🟢 a decrease, ⚪ no change, 🆕 an export that does not exist on the base (never a regression), 🗑️ an export that was removed. An intentional increase is accepted with the |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## claude/get-cnpj-info #562 +/- ##
======================================================
Coverage 100.00% 100.00%
======================================================
Files 192 194 +2
Lines 2101 2109 +8
Branches 619 621 +2
======================================================
+ Hits 2101 2109 +8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
8c13c81 to
2b00546
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
|
a42c871 to
4c578a1
Compare
4c578a1 to
c8df7a8
Compare
…range Knowing the state of a CEP so far took a network call to a CEP API. The Correios assign every state one or two ranges of CEPs, so the state can be answered offline from a 30 row table: Amazonas, Distrito Federal and Goiás have two ranges each, and 00000-000 to 00999-999 and 78900-000 to 78999-999 belong to no state and answer null. The value goes through isValidCep and parseCep, and a number has to be a non-negative integer, as in getStateByIbgeCode. The result is the same State object the other state utils return.
The lookup walked the 27 states and re-scanned the 30 ranges for each one, up to 810 comparisons for every call. The question is which range holds the CEP, so the range table is the outer loop: at most 57 comparisons, and the shape reads like the sibling getStateByIbgeCode. Also say in the docs that a range is the block the state owns and not a promise that every CEP in it is in use, since 10000-000 to 10999-999 sits unused inside the range of São Paulo, and cover that block and the shape of the table (ascending, no overlap, one inner gap) with tests.
c8df7a8 to
9a904ee
Compare
… municipality table getStateByCep reads its range table through a new internal, _internals/find-cep-range, which validates and parses the CEP once and returns the range that holds it, so the lookup is written once. The IBGE municipality table moves from src/_internals/constants/cities.ts to municipalities.ts: it holds municipalities (with their codes and states), not a list of city names, and every util that reads it now imports it under that name. The generated file is unchanged apart from its path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLkm9YrtAifc6XCLFVEsdH
9a904ee to
8047b20
Compare
Update (2026-09-27)
getMunicipalityByCepwas removed from this PR and from its history: the maintainer decided to ship it later, generated from an official Correios export. The community gist its table was generated from (pinned revision ofhugosenari/ec1a7d88…) turned out to be out of date: 406 ranges started at-001instead of-000(for examplegetMunicipalityByCep("01000-000")wasnullfor São Paulo), and 135 municipalities differed from the current Correios faixas after the recodings since 2020 (for example33230-000read as Vespasiano instead of Lagoa Santa). Its range table, its generator (scripts/municipality-cep-ranges.ts) and thescripts/data.tsstep that ran it, theOTHER_NAMESspellings, its docs, CONTRIBUTING notes, JSR entry, API report line and bundle-size rows went with it. What stays:getStateByCep, the shared_internals/find-cep-rangelookup, and the rename of the internal IBGE municipality tablecities.ts→municipalities.ts.What
One offline CEP range lookup, plus one naming cleanup:
getStateByCep: the state (UF) that owns a CEP, from the CEP ranges the Correios assign to each state. A 30 row pure literal insrc/get-state-by-cep/constants.ts.findCepRange(src/_internals/find-cep-range/find-cep-range.ts), so a later CEP range lookup can reuse it.src/_internals/constants/cities.tstomunicipalities.ts: it exportsMunicipality, notCity, and nothing public imports the path directly, so this is not a breaking change. No public export is renamed.No network call: it answers from a range table, not a CEP API.
API
isValidCepaccepts: 8 digits, string or number, with spaces, dots and hyphens ignored. A number must be a non-negative integer (isLookupCode), so-20040020and2004002.5arenullinstead of being read as a CEP. A CEP starting with0has to be a string, as inisValidCep.Stateobject asgetStates/getStateByIbgeCode(a fresh copy), so it composes with the rest of the state family.nullfor an invalid CEP, for any non string/number input, and for a CEP outside every range.Ranges in the
getStateByCeptable (three states have two):Sources
22930-869printed under Espírito Santo). It confirms, among others, the Goiás municipalities around Brasília at72800-001and up, Distrito Federal up to73405-999, Mato Grosso up to78899-999and Rondônia from76800-001.The 30
getStateByCepboundaries are cross-verifiedThey do not rest on the
tamnilgist alone. Every boundary was re-derived from sources independent of it, and all 30 match the table:72774-999against GO72800-001, GO72979-999against DF73000-001, MT78899-999).hugosenaricommunity CSV of per-municipality Correios CEP ranges (https://gist.github.com/hugosenari/ec1a7d88f5bdd01844424dbc9aff9590, 5,764 rows): mapping both endpoints of every row through this 30 row UF table gives 0 mismatches over 11,528 endpoints, and the CSV pins each of the 30 boundaries to the adjacent CEP the same way the PDF does (for example PA68899-999against AP68900-001). It is out of date at the municipality level (see the Update above), but its state boundaries agree with every other source.69399-000Cantá/RR and69400-970Manacapuru/AM,76801-000Porto Velho/RO,73700-000Padre Bernardo/GO,72800-010Luziânia/GO,68890-000Afuá/PA,68900-010Macapá/AP,79002-000Campo Grande/MS.JoseQuintas/sefazclass(json/sefazcepuf.json),klawdyo/validation-br(src/cep.ts),pdrodavi/cep2uf, and the carrier and e-commerce tables of bring.com.br, blog.shoppub.com.br and ajuda.lojaintegrada.com.br.00000-000to00999-999and78900-000to78999-999).Two secondary sources disagree, and both are wrong:
78899-999, not78999-999. The Wikipedia zone table lists "MT Interior | 78110 - 78999", which would close the789xxgap. The official PDF's highest MT CEP is78899-999(Sorriso) and it lists no CEP at all in789xx; thehugosenariCSV has none of its municipality ranges there either; live ViaCEP answers{"erro":true}for78900-000,78950-000and78999-000.789xxis the range Rondônia vacated when it was moved to768xx, which is the same move that took Goiás down to76799-999. The gap stays.datasets-br/state-codesrecords SP as01000-000–09999-999plus11000-000–19999-999. The Correios UF faixa, which is what this table copies, gives SP a single01000-000–19999-999, and so does every other source checked.getStateByCep("10000-000")therefore answers SP although no city uses10xxx: the São Paulo capital ranges skip over10xxx, the "Localidades alvo" PDF has no CEP starting with10anywhere in its 76 pages, and live ViaCEP answers{"erro":true}for10000-000. A faixa is the block the state owns, not a guarantee that every CEP in it is in use, and SP has other unused blocks. The JSDoc and both docs say so.Naming: the internal municipality table
Looked at
git log, the JSDoc ofgetCities/getMunicipalityandCONTRIBUTING.md; there is no mention of a planned rename of any public export. Commitd313bcc2(docs(municipalities): deprecate getCities and getMunicipality in favour of the municipality family) already settled the public naming onmain:So no public export is renamed in this PR (that would be the exact breaking change the release must avoid). What did move: the internal
_internals/constants/cities.tstomunicipalities.ts, since it exportsMunicipality, is behindgetMunicipalities/getMunicipalityByCode/getMunicipality/getCitiesalike, and nothing public imports its path. The generated file is unchanged apart from its path;scripts/cities.ts,scripts/data.tsandscripts/data-summary.tspoint at the new path.Verification
npm run check: passnpm run test -- --run: passnpm run test:coverage: 100% statements, branches, functions and linesnpm run build,npm run check:api:update: pass, report committed (one new export:getStateByCep;Municipalityre-exported from its new path)npm run check:unused: passnpm run check:duplication: 0 clonesnpm run check:tree-shaking:getStateByCep4620 B / 1565 B gzipnpm run check:commits: passnpm run test:mutation -- --mutate 'src/_internals/find-cep-range/find-cep-range.ts': 100% (17 killed, 0 survived)npm run test:mutation -- --mutate 'src/get-state-by-cep/get-state-by-cep.ts': 100% (9 killed, 0 survived)bun test srcandnpm run test:deno: passnpm run build:docsandnpm run build:jsr: run, output committed (jsr.jsongains./get-state-by-cep)Open points
getStateByCepranges were not read from the official Correios search itself, because it is behind a CAPTCHA that I did not try to bypass. They come from third party copies of that search, corroborated as described above.src/get-state-by-cep/constants.ts, with no generator underscripts/, because the only official source cannot be fetched by a script.66000-000to68899-999); the task mentioned it as historically split, and no source checked lists more than one range for it.isValidCepreads-20040020and2004002.5as valid CEPs today;getStateByCeprejects them as numbers (they stay accepted as strings such as"20040-020"). That util was left untouched.Rebase onto #560
Rebased from
mainontoclaude/get-cnpj-info, so this branch now carries #558, #559 and #560 underneath it. Conflicts resolved:docs/llms.txtanddocs/llms-full.txtare no longer tracked (they are generated now), so both weregit rm-ed.getStateByCepsection ofdocs/utilities.mdanddocs/pt-br/utilities.mdwas ported into the new per-utility format of feat: Standard Schema wrapper, JSR, pkg.pr.new, docs previews and a playground #556: a short paragraph, a bullet list for the accepted input and the edge cases, thejavascriptblock, and aSource:line pointing at the Correios "Busca Faixa de CEP".src/index.tsandsrc/index.test.tskept strictly alphabetical, betweengetPixPayloadInfoandgetStateByIbgeCode.jsr.json(new onmain) regenerated withnpm run build:jsr, andreports/api/brazilian-utils.api.mdwithnpm run check:api:update. Both are folded into the commits that own them, with no separate "regenerate" commit.Re-verified on the rebased branch:
npm run check,npm run test:coverage(100% statements, branches, functions and lines),npm run build,npm run check:unused,npm run check:duplicationandnpm run check:commitsall pass.Summary by CodeRabbit
New Features
nullfor invalid, uncovered, or unsupported ranges.Documentation